package com.milowski.monos.monomial;

import com.milowski.monos.*;
import java.util.regex.Pattern;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;

/**
 * A monomial and its exponent vector  .  While the monomial does not contain
 * a lexicographic ordering, it is usually constructed with one in mind.  That
 * is, the exponent vector contained within each instance assumes a particular
 * ordering.
 */
public class Monomial implements XMLMarshaller {
   short [] values;

   /**
    * Creates a monomial of a specific exponent vector size.
    * @param size The dimension of the exponent vector.
    */
   public Monomial(int size) {
      values = new short[size];
   }
   
   /**
    * Creates a monomial with the exponent vector specified. 
    * @param values The exponent vector to use.  These values are copied.
    */
   public Monomial(short [] values) {
      this(values,true);
   }
   public Monomial(short [] values,boolean copy) {
      if (copy) {
         this.values = new short[values.length];
         System.arraycopy(values,0,this.values,0,values.length);
      } else {
         this.values = values;
      }
   }

   /**
    * Creates a monomial consisting of a single variable to a power.
    * @param index The index of the value to use.
    * @param values The exponent vector to use.  
    */
   public Monomial(int index,short [] values) {
      this.values = new short[values.length];
      for (int i=0; i<this.values.length; i++) {
         this.values[i] = 0;
      }
      this.values[index] = values[index];
      if (values[index]==0) {
         throw new RuntimeException("Invalid monomial with exponent zero.");
      }
   }
   
   /**
    * Creates a monomial consisting of a single variable to a power.
    * @param index The index of the value to use.
    * @param value The value to use. 
    */
   public Monomial(int index,short value) {
      this.values = new short[values.length];
      for (int i=0; i<this.values.length; i++) {
         this.values[i] = 0;
      }
      this.values[index] = value;
      if (values[index]==0) {
         throw new RuntimeException("Invalid monomial with exponent zero.");
      }
   }
   
   /**
    * Returns 'true' if this monomial divides the argument.
    */
   public boolean divides(Monomial other) {
      short [] ovalues = other.getValues();
      if (ovalues.length>values.length) {
         return false;
      }
      for (int i=0; i<values.length; i++) {
         if (values[i]>ovalues[i]) {
            return false;
         }
      }
      return true;
   }
   
   /**
    * Returns 'true' if this monomial equals the argument.
    */
   public boolean equals(Object other) {
      if (other instanceof Monomial) {
         Monomial m = (Monomial)other;
         short [] ovalues = m.getValues();
         if (ovalues.length==values.length) {
            for (int i=0; i<values.length; i++) {
               if (values[i]!=ovalues[i]) {
                  return false;
               }
            }
            return true;
         }
      }
      return false;
   }
   
   /**
    * Returns 'true' if this monomial is "before" the other in their lexicographic
    * order.
    */
   public boolean before(Monomial other) {
      short [] ovalues = other.getValues();
      for (int i=0; i<values.length; i++) {
         if (values[i]>ovalues[i]) {
            return true;
         } else if (ovalues[i]>values[i]) {
            return false;
         }
      }
      return false;
   }
      
   /**
    * Returns 'true' if this monomial is "before" the other in the given lexicographic
    * order.
    * @param lexOrder A array of indices that indicates the order to compare.
    * @param other The monomial to compare.
    */
   public boolean before(int [] lexOrder,Monomial other) {
      short [] ovalues = other.getValues();
      for (int i=0; i<values.length; i++) {
         if (values[lexOrder[i]]>ovalues[lexOrder[i]]) {
            return true;
         } else if (ovalues[lexOrder[i]]>values[lexOrder[i]]) {
            return false;
         }
      }
      return false;
   }

   /**
    * Returns the exponent vector of the monomial.
    */
   public short [] getValues() {
      return values;
   }

   /**
    * Formats the monomial into a string representation using the lexicographic
    * ordering specified. 
    * @param order The ordering to use.
    */
   public String format(LexOrder order) {
      StringBuffer sb = new StringBuffer();
      boolean started = false;
      for (int i=0; i<values.length; i++) {
         if (values[i]!=0) {
            if (started) {
               sb.append('*');
            }
            started = true;
            sb.append((String)order.get(i));
            if (values[i]!=1) {
               sb.append('^');
               sb.append(Short.toString(values[i]));
            }
         }
      }
      return sb.toString();
   }

   /**
    * Creates a string representation (vector format) of the monomial.
    */
   public String toString() {
      StringBuffer sb = new StringBuffer();
      sb.append("(list->monomial ");
      for (int i=0; i<values.length; i++) {
         if (i!=0) {
            sb.append(' ');
         }
         sb.append(values[i]);
      }
      sb.append(')');
      return sb.toString();
   }
   
   public static void parseMonomial(LexOrder order,short [] values,String exp)
      throws FormatException
   {
      parseMonomial(order,values,exp,false);
   }
   
   public static void parseMonomial(LexOrder order,short [] values,String exp,boolean negate)
      throws FormatException
   {
      do {
         int multiply = exp.indexOf('*');
         String varExpr = null;
         if (multiply<0) {
            varExpr = exp.trim();
            exp = "";
         } else {
            varExpr = exp.substring(0,multiply).trim();
            exp = exp.substring(multiply+1);
         }
         int circum = varExpr.indexOf('^');
         short power = (short)1;
         String varName = varExpr;
         if (circum==0) {
            throw new FormatException("Missing variable name in expression: "+varExpr);
         } else if (circum>0) {
            varName = varExpr.substring(0, circum).trim();
            power = Short.parseShort(varExpr.substring(circum+1));
         }
         int pos = order.getOrder(varName);
         if (pos<0) {
            throw new FormatException("Unknown variable name in expression: "+varName);
         }
         values[pos] = negate ? (short)(values[pos]-power) : power;
      } while (exp.length()!=0);
   }

   public static Monomial toMonomial(LexOrder order,String exp) 
      throws FormatException
   {
      return toMonomial(order,exp,false);
   }
   
   public static Monomial toMonomial(LexOrder order,String exp,boolean negate) 
      throws FormatException
   {
      Monomial m = new Monomial(order.size());
      short [] values = m.getValues();
      parseMonomial(order, values,exp,negate);
      return m;
   }

   public void toXML(ItemDestination dest) throws XMLException {
      toXML(InfosetFactory.getDefaultInfoset().createItemConstructor(),dest);
   }

   public void toXML(ItemConstructor constructor, ItemDestination dest) throws XMLException {
      toXML(constructor,dest,values);
   }
   
   public static void toXML(ItemConstructor constructor, ItemDestination dest, short [] mvalues) throws XMLException {
      dest.send(constructor.createElement(XML.monomialName));
      StringBuffer sb = new StringBuffer();
      for (int i=0; i<mvalues.length; i++) {
         if (i!=0) {
            sb.append(' ');
         }
         sb.append(mvalues[i]);
      }
      dest.send(constructor.createCharacters(sb.toString()));
      dest.send(constructor.createElementEnd(XML.monomialName));
   }

   static Pattern pattern = Pattern.compile("\\s+");
   
   public static Monomial fromXML(Element representation) throws XMLException {
      String [] svalues = pattern.split(representation.getText().trim());
      Monomial m = new Monomial(svalues.length);
      short [] values = m.getValues();
      for (int i=0; i<values.length; i++) {
         values[i] = Short.parseShort(svalues[i]);
      }
      return m;
   }
}
   

